home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Celestin Apprentice 7
/
Apprentice-Release7.iso
/
Environments
/
PowerLisp 2.01
/
Supplemental Documentation
/
Documentation
/
Chapter 19. Structures
< prev
next >
Wrap
Text File
|
1995-03-27
|
49KB
|
1,054 lines
Common Lisp the Language, 2nd Edition
-------------------------------------------------------------------------------
19. Structures
Common Lisp provides a facility for creating named record structures with named
components. In effect, the user can define a new data type; every data
structure of that type has components with specified names. Constructor,
access, and assignment constructs are automatically defined when the data type
is defined.
This chapter is divided into two parts. The first part discusses the basics of
the structure facility, which is very simple and allows the user to take
advantage of the type-checking, modularity, and convenience of user-defined
record data types. The second part, beginning with section 19.5, discusses a
number of specialized features of the facility that have advanced applications.
These features are completely optional, and you needn't even know they exist in
order to take advantage of the basics.
-------------------------------------------------------------------------------
* Introduction to Structures
* How to Use Defstruct
* Using the Automatically Defined Constructor Function
* Defstruct Slot-Options
* Defstruct Options
* By-Position Constructor Functions
* Structures of Explicitly Specified Representational Type
o Unnamed Structures
o Named Structures
o Other Aspects of Explicitly Specified Structures
-------------------------------------------------------------------------------
19.1. Introduction to Structures
The structure facility is embodied in the defstruct macro, which allows the
user to create and use aggregate data types with named elements. These are like
``structures'' in PL/I, or ``records'' in Pascal.
As an example, assume you are writing a Lisp program that deals with space
ships in a two-dimensional plane. In your program, you need to represent a
space ship by a Lisp object of some kind. The interesting things about a space
ship, as far as your program is concerned, are its position (represented as x
and y coordinates), velocity (represented as components along the x and y
axes), and mass.
A ship might therefore be represented as a record structure with five
components: x-position, y-position, x-velocity, y-velocity, and mass. This
structure could in turn be implemented as a Lisp object in a number of ways. It
could be a list of five elements; the x-position could be the car, the
y-position the cadr, and so on. Equally well it could be a vector of five
elements: the x-position could be element 0, the y-position element 1, and so
on. The problem with either of these representations is that the components
occupy places in the object that are quite arbitrary and hard to remember.
Someone looking at (cadddr ship1) or (aref ship1 3) in a piece of code might
find it difficult to determine that this is accessing the y-velocity component
of ship1. Moreover, if the representation of a ship should have to be changed,
it would be very difficult to find all the places in the code to be changed to
match (not all occurrences of cadddr are intended to extract the y-velocity
from a ship).
Ideally components of record structures should have names. One would like to
write something like (ship-y-velocity ship1) instead of (cadddr ship1). One
would also like a more mnemonic way to create a ship than this:
(list 0 0 0 0 0)
Indeed, one would like ship to be a new data type, just like other Lisp data
types, that one could test with typep, for example. The defstruct facility
provides all of this.
defstruct itself is a macro that defines a structure. For the space ship
example, one might define the structure by saying:
(defstruct ship
x-position
y-position
x-velocity
y-velocity
mass)
This declares that every ship is an object with five named components. The
evaluation of this form does several things:
* It defines ship-x-position to be a function of one argument, a ship, that
returns the x-position of the ship; ship-y-position and the other
components are given similar function definitions. These functions are
called the access functions, as they are used to access elements of the
structure.
* The symbol ship becomes the name of a data type of which instances of
ships are elements. This name becomes acceptable to typep, for example;
(typep x 'ship) is true if x is a ship and false if x is any object other
than a ship.
* A function named ship-p of one argument is defined; it is a predicate
that is true if its argument is a ship and is false otherwise.
* A function called make-ship is defined that, when invoked, will create a
data structure with five components, suitable for use with the access
functions. Thus executing
(setq ship2 (make-ship))
sets ship2 to a newly created ship object. One can specify the initial
values of any desired component in the call to make-ship by using keyword
arguments in this way:
(setq ship2 (make-ship :mass *default-ship-mass*
:x-position 0
:y-position 0))
This constructs a new ship and initializes three of its components. This
function is called the constructor function because it constructs a new
structure.
* The #S syntax can be used to read instances of ship structures, and a
printer function is provided for printing out ship structures. For
example, the value of the variable ship2 shown above might be printed as
#S(ship x-position 0 y-position 0 x-velocity nil
y-velocity nil mass 170000.0)
* A function called copy-ship of one argument is defined that, when given a
ship object, will create a new ship object that is a copy of the given
one. This function is called the copier function.
* One may use setf to alter the components of a ship:
(setf (ship-x-position ship2) 100)
This alters the x-position of ship2 to be 100. This works because
defstruct behaves as if it generates an appropriate defsetf form for each
access function.
This simple example illustrates the power of defstruct to provide abstract
record structures in a convenient manner. defstruct has many other features as
well for specialized purposes.
-------------------------------------------------------------------------------
19.2. How to Use Defstruct
All structures are defined through the defstruct construct. A call to defstruct
defines a new data type whose instances have named slots.
[change_begin]
[Macro]
defstruct name-and-options [doc-string] {slot-description}+
X3J13 voted in June 1988 (DEFSTRUCT-SLOTS-CONSTRAINTS-NUMBER) to allow a
defstruct definition to have no slot-description at all; in other words, the
occurrence of slot-description in the preceding header line would be replaced
by slot-description.
Such structure definitions are particularly useful if the :include option is
used, perhaps with other options; for example, one can have two structures that
are exactly alike except that they print differently (having different
:print-function options).
Implementors are encouraged to permit this simple extension as soon as
convenient. Users, however, may wish to maximize portability of their code by
avoiding the use of this extension unless and until it is adopted as part of
the ANSI standard.
[change_end]
This defines a record-structure data type. A general call to defstruct looks
like the following example.
(defstruct (name option-1 option-2 ... option-m)
doc-string
slot-description-1
slot-description-2
...
slot-description-n)
The name must be a symbol; it becomes the name of a new data type consisting of
all instances of the structure. The function typep will accept and use this
name as appropriate. The name is returned as the value of the defstruct form.
Usually no options are needed at all. If no options are specified, then one may
write simply name instead of (name) after the word defstruct. The syntax of
options and the options provided are discussed in section 19.5.
If the optional documentation string doc-string is present, then it is attached
to the name as a documentation string of type structure; see documentation.
Each slot-description-j is of the form
(slot-name default-init slot-option-name-1 slot-option-value-1
slot-option-name-2 slot-option-value-2 ... slot-option-name-kj
slot-option-value-kj)
Each slot-name must be a symbol; an access function is defined for each slot.
If no options and no default-init are specified, then one may write simply
slot-name instead of (slot-name) as the slot description.
[old_change_begin]
The default-init is a form that is evaluated each time a structure is to be
constructed; the value is used as the initial value of the slot.
[old_change_end]
[change_begin]
X3J13 voted in October 1988 (DEFSTRUCT-DEFAULT-VALUE-EVALUATION) to clarify
that a default-init form is evaluated only if the corresponding argument is not
supplied to the constructor function. The preceding sentence should therefore
read as follows:
The default-init is a form that is evaluated each time its value is to be used
as the initial value of the slot.
[change_end]
If no default-init is specified, then the initial contents of the slot are
undefined and implementation-dependent. The available slot-options are
described in section 19.4.
-------------------------------------------------------------------------------
Compatibility note: Slot-options are not currently provided in Lisp Machine
Lisp, but this is an upward-compatible extension.
-------------------------------------------------------------------------------
[change_begin]
X3J13 voted in January 1989 (DEFSTRUCT-SLOTS-CONSTRAINTS-NAME) to specify
that it is an error for two slots to have the same name; more precisely, no two
slots may have names for whose print names string= would be true. Under this
interpretation
(defstruct lotsa-slots slot slot)
obviously is incorrect but the following one is also in error, even assuming
that the symbols coin:slot and blot:slot really are distinct (non-eql) symbols:
(defstruct no-dice coin:slot blot:slot)
To illustrate another case, the first defstruct form below is correct, but the
second one is in error.
(defstruct one-slot slot) (defstruct (two-slots (:include one-slot)) slot)
-------------------------------------------------------------------------------
Rationale: Print names are the criterion for slot-names being the same, rather
than the symbols themselves, because defstruct constructs names of accessor
functions from the print names and interns the resulting new names in the
current package.
-------------------------------------------------------------------------------
X3J13 recommended that expanding a defstruct form violating this restriction
should signal an error and noted, with an eye to the Common Lisp Object System
(CLOS) , that the restriction applies only to the operation of the defstruct
macro as such and not to the structure-class or structures defined with
defclass.
X3J13 voted in March 1989 (DEFINING-MACROS-NON-TOP-LEVEL) to clarify that,
while defining forms normally appear at top level, it is meaningful to place
them in non-top-level contexts; defstruct must treat slot default-init forms
and any initialization forms within the specification of a by-position
constructor function as occurring within the enclosing lexical environment, not
within the global environment.
[change_end]
defstruct not only defines an access function for each slot, but also arranges
for setf to work properly on such access functions, defines a predicate named
name-p, defines a constructor function named make-name, and defines a copier
function named copy-name. All names of automatically created functions are
interned in whatever package is current at the time the defstruct form is
processed (see *package*). Also, all such functions may be declared inline at
the discretion of the implementation to improve efficiency; if you do not want
some function declared inline, follow the defstruct form with a notinline
declaration to override any automatic inline declaration.
[change_begin]
X3J13 voted in January 1989 (DEFSTRUCT-REDEFINITION) to specify that the
results of redefining a defstruct structure (that is, evaluating more than one
defstruct structure for the same name) are undefined.
The problem is that if instances have been created under the old definition and
then remain accessible after the new definition has been evaluated, the
accessors and other functions for the new definition may be incompatible with
the old instances. Conversely, functions associated with the old definition may
have been declared inline and compiled into code that remains accessible after
the new definition has been evaluated; such code may be incompatible with the
new instances.
In practice this restriction affects the development and debugging process
rather than production runs of fully developed code. The defstruct feature is
intended to provide ``the most efficient'' structure class. CLOS classes
defined by defclass allow much more flexible structures to be defined and
redefined.
Programming environments are allowed and encouraged to permit defstruct
redefinition, perhaps with warning messages about possible interactions with
other parts of the programming environment or memory state. It is beyond the
scope of the Common Lisp language standard to define those interactions except
to note that they are not portable.
[change_end]
-------------------------------------------------------------------------------
19.3. Using the Automatically Defined Constructor Function
After you have defined a new structure with defstruct, you can create instances
of this structure by using the constructor function. By default, defstruct
defines this function automatically. For a structure named foo, the constructor
function is normally named make-foo; you can specify a different name by giving
it as the argument to the :constructor option, or specify that you don't want a
normal constructor function at all by using nil as the argument (in which case
one or more ``by-position'' constructors should be requested; see section
19.6).
A call to a constructor function, in general, has the form
(name-of-constructor-function
slot-keyword-1 form-1
slot-keyword-2 form-2
...)
All arguments are keyword arguments. Each slot-keyword should be a keyword
whose name matches the name of a slot of the structure (defstruct determines
the possible keywords simply by interning each slot-name in the keyword
package). All the keywords and forms are evaluated. In short, it is just as if
the constructor function took all its arguments as &key parameters. For
example, the ship structure shown in section 19.1 has a constructor function
that takes arguments roughly as if its definition were
(defun make-ship (&key x-position y-position
x-velocity y-velocity mass)
...)
If slot-keyword-j names a slot, then that element of the created structure
will be initialized to the value of form-j. If no pair slot-keyword-j and
form-j is present for a given slot, then the slot will be initialized by
evaluating the default-init form specified for that slot in the call to
defstruct. (In other words, the initialization specified in the defstruct
defers to any specified in a call to the constructor function.) If the default
initialization form is used, it is evaluated at construction time, but in the
lexical environment of the defstruct form in which it appeared. If the
defstruct itself also did not specify any initialization, the element's initial
value is undefined. You should always specify the initialization, either in the
defstruct or in the call to the constructor function, if you care about the
initial value of the slot.
Each initialization form specified for a defstruct component, when used by the
constructor function for an otherwise unspecified component, is re-evaluated on
every call to the constructor function. It is as if the initialization forms
were used as init forms for the keyword parameters of the constructor function.
For example, if the form (gensym) were used as an initialization form, either
in the constructor-function call or as the default initialization form in the
defstruct form, then every call to the constructor function would call gensym
once to generate a new symbol.
[change_begin]
X3J13 voted in October 1988 (DEFSTRUCT-DEFAULT-VALUE-EVALUATION) to clarify
that the default value in a defstruct slot is not evaluated unless it is needed
in the creation of a particular structure instance. If it is never needed,
there can be no type-mismatch error, even if the type of the slot is specified,
and no warning should be issued.
For example, in the following sequence only the last form is in error.
(defstruct person (name .007 :type string))
(make-person :name "James")
(make-person) ;Error to give name the value .007
[change_end]
-------------------------------------------------------------------------------
19.4. Defstruct Slot-Options
Each slot-description in a defstruct form may specify one or more slot-options.
A slot-option consists of a pair of a keyword and a value (which is not a form
to be evaluated, but the value itself). For example:
(defstruct ship
(x-position 0.0 :type short-float)
(y-position 0.0 :type short-float)
(x-velocity 0.0 :type short-float)
(y-velocity 0.0 :type short-float)
(mass *default-ship-mass* :type short-float :read-only t))
This specifies that each slot will always contain a short-format floating-point
number, and that the last slot may not be altered once a ship is constructed.
The available slot-options are as follows.
:type
The option :type type specifies that the contents of the slot will always
be of the specified data type. This is entirely analogous to the
declaration of a variable or function; indeed, it effectively declares the
result type of the access function. An implementation may or may not
choose to check the type of the new object when initializing or assigning
to a slot. Note that the argument form type is not evaluated; it must be a
valid type specifier.
:read-only
The option :read-only x, where x is not nil, specifies that this slot may
not be altered; it will always contain the value specified at construction
time. setf will not accept the access function for this slot. If x is nil,
this slot-option has no effect. Note that the argument form x is not
evaluated.
Note that it is impossible to specify a slot-option unless a default value is
specified first.
-------------------------------------------------------------------------------
19.5. Defstruct Options
The preceding description of defstruct is all that the average user will need
(or want) to know in order to use structures. The remainder of this chapter
discusses more complex features of the defstruct facility.
This section explains each of the options that can be given to defstruct. A
defstruct option may be either a keyword or a list of a keyword and arguments
for that keyword. (Note that the syntax for defstruct options differs from the
pair syntax used for slot-options. No part of any of these options is
evaluated.)
:conc-name
This provides for automatic prefixing of names of access functions. It is
conventional to begin the names of all the access functions of a structure
with a specific prefix, the name of the structure followed by a hyphen.
This is the default behavior.
The argument to the :conc-name option specifies an alternative prefix to
be used. (If a hyphen is to be used as a separator, it must be specified
as part of the prefix.) If nil is specified as an argument, then no prefix
is used; then the names of the access functions are the same as the
slot-names, and it is up to the user to name the slots reasonably.
Note that no matter what is specified for :conc-name, with a constructor
function one uses slot keywords that match the slot-names, with no prefix
attached. On the other hand, one uses the access-function name when using
setf. Here is an example:
(defstruct door knob-color width material)
(setq my-door
(make-door :knob-color 'red :width 5.0))
(door-width my-door) => 5.0
(setf (door-width my-door) 43.7)
(door-width my-door) => 43.7
(door-knob-color my-door) => red
:constructor
This option takes one argument, a symbol, which specifies the name of the
constructor function. If the argument is not provided or if the option
itself is not provided, the name of the constructor is produced by
concatenating the string "MAKE-" and the name of the structure, putting
the name in whatever package is current at the time the defstruct form is
processed (see *package*). If the argument is provided and is nil, no
constructor function is defined.
This option actually has a more general syntax that is explained in
section 19.6.
:copier
This option takes one argument, a symbol, which specifies the name of the
copier function. If the argument is not provided or if the option itself
is not provided, the name of the copier is produced by concatenating the
string "COPY-" and the name of the structure, putting the name in whatever
package is current at the time the defstruct form is processed (see
*package*). If the argument is provided and is nil, no copier function is
defined.
The automatically defined copier function simply makes a new structure and
transfers all components verbatim from the argument into the newly created
structure. No attempt is made to make copies of the components.
Corresponding components of the old and new structures will therefore be
eql.
:predicate
This option takes one argument, which specifies the name of the type
predicate. If the argument is not provided or if the option itself is not
provided, the name of the predicate is made by concatenating the name of
the structure to the string "-P", putting the name in whatever package is
current at the time the defstruct form is processed (see *package*). If
the argument is provided and is nil, no predicate is defined. A predicate
can be defined only if the structure is ``named''; if the :type option is
specified and the :named option is not specified, then the :predicate
option must either be unspecified or have the value nil.
:include
This option is used for building a new structure definition as an
extension of an old structure definition. As an example, suppose you have
a structure called person that looks like this:
(defstruct person name age sex)
Now suppose you want to make a new structure to represent an astronaut.
Since astronauts are people too, you would like them also to have the
attributes of name, age, and sex, and you would like Lisp functions that
operate on person structures to operate just as well on astronaut
structures. You can do this by defining astronaut with the :include
option, as follows:
(defstruct (astronaut (:include person)
(:conc-name astro-))
helmet-size
(favorite-beverage 'tang))
The :include option causes the structure being defined to have the same
slots as the included structure. This is done in such a way that the
access functions for the included structure will also work on the
structure being defined. In this example, an astronaut will therefore have
five slots: the three defined in person and the two defined in astronaut
itself. The access functions defined by the person structure can be
applied to instances of the astronaut structure, and they will work
correctly. Moreover, astronaut will have its own access functions for
components defined by the person structure. The following examples
illustrate how you can use astronaut structures:
(setq x (make-astronaut :name 'buzz
:age 45
:sex t
:helmet-size 17.5))
(person-name x) => buzz
(astro-name x) => buzz
(astro-favorite-beverage x) => tang
The difference between the access functions person-name and astro-name is
that person-name may be correctly applied to any person, including an
astronaut, while astro-name may be correctly applied only to an astronaut.
(An implementation may or may not check for incorrect use of access
functions.)
At most one :include option may be specified in a single defstruct form.
The argument to the :include option is required and must be the name of
some previously defined structure. If the structure being defined has no
:type option, then the included structure must also have had no :type
option specified for it. If the structure being defined has a :type
option, then the included structure must have been declared with a :type
option specifying the same representation type.
If no :type option is involved, then the structure name of the including
structure definition becomes the name of a data type, of course, and
therefore a valid type specifier recognizable by typep; moreover, it
becomes a subtype of the included structure. In the above example,
astronaut is a subtype of person; hence
(typep (make-astronaut) 'person)
is true, indicating that all operations on persons will also work on
astronauts.
The following is an advanced feature of the :include option. Sometimes,
when one structure includes another, the default values or slot-options
for the slots that came from the included structure are not what you want.
The new structure can specify default values or slot-options for the
included slots different from those the included structure specifies, by
giving the :include option as
(:include name slot-description-1 slot-description-2 ...)
Each slot-description-j must have a slot-name or slot-keyword that is the
same as that of some slot in the included structure. If slot-description-j
has no default-init, then in the new structure the slot will have no
initial value. Otherwise its initial value form will be replaced by the
default-init in slot-description-j. A normally writable slot may be made
read-only. If a slot is read-only in the included structure, then it must
also be so in the including structure. If a type is specified for a slot,
it must be the same as, or a subtype of, the type specified in the
included structure. If it is a strict subtype, the implementation may or
may not choose to error-check assignments.
For example, if we had wanted to define astronaut so that the default age
for an astronaut is 45, then we could have said:
(defstruct (astronaut (:include person (age 45)))
helmet-size
(favorite-beverage 'tang))
[change_begin]
X3J13 voted in June 1988 (DATA-TYPES-HIERARCHY-UNDERSPECIFIED) to
require any structure type created by defstruct (or defclass) to be
disjoint from any of the types cons, symbol, array, number, character,
hash-table, readtable, package, pathname, stream, and random-state. A
consequence of this requirement is that it is an error to specify any of
these types, or any of their subtypes, to the defstruct :include option.
(The first edition said nothing explicitly about this. Inasmuch as using
such a type with the :include option was not defined to work, one might
argue that such use was an error in Common Lisp as defined by the first
edition.)
[change_end]
:print-function
This option may be used only if the :type option is not specified. The
argument to the :print-function option should be a function of three
arguments, in a form acceptable to the function special form, to be used
to print structures of this type. When a structure of this type is to be
printed, the function is called on three arguments: the structure to be
printed, a stream to print to, and an integer indicating the current depth
(to be compared against *print-level*). The printing function should
observe the values of such printer-control variables as *print-escape* and
*print-pretty*.
If the :print-function option is not specified and the :type option also
not specified, then a default printing function is provided for the
structure that will print out all its slots using #S syntax (see section
22.1.4).
[change_begin]
X3J13 voted in January 1989 (PRINT-CIRCLE-STRUCTURE) to specify that
user-defined printing functions for the defstruct :print-function option
may print objects to the supplied stream using write, print1, princ,
format, or print-object and expect circularities to be detected and
printed using #n# syntax (when *print-circle* is non-nil, of course). See
*print-circle*.
X3J13 voted in January 1989 (DEFSTRUCT-PRINT-FUNCTION-INHERITANCE) to
clarify that if the :print-function option is not specified but the
:include option is specified, then the print function is inherited from
the included structure type. Thus, for example, an astronaut will be
printed by the same printing function that is used for person.
X3J13 in the same vote extended the print-function option as follows: If
the print-function option is specified but with no argument, then the
standard default printing function (that uses #S syntax) will be used.
This provides a means of overriding the inheritance rule. For example, if
person and astronaut had been defined as
(defstruct (person
(:print-function ;Special print function
(lambda (p s k)
(format s "<~A, age ~D>"
(person-name p)
(person-age p)))))
name age sex)
(defstruct (astronaut
(:include person)
(:conc-name astro-)
(:print-function)) ;Use default print function
helmet-size
(favorite-beverage 'tang))
then an ordinary person would be printed as ``<Joe Schmoe, age 27>'' but
an astronaut would be printed as, for example,
#S(ASTRONAUT NAME BUZZ AGE 45 SEX T
HELMET-SIZE 17.5 FAVORITE-BEVERAGE TANG)
using the default #S syntax (yuk).
[change_end]
These changes make the behavior of defstruct with respect to the :include
option a bit more like the behavior of classes in CLOS.
:type
The :type option explicitly specifies the representation to be used for
the structure. It takes one argument, which must be one of the types
enumerated below.
Specifying this option has the effect of forcing a specific representation
and of forcing the components to be stored in the order specified in the
defstruct form in corresponding successive elements of the specified
representation. It also prevents the structure name from becoming a valid
type specifier recognizable by typep (see section 19.7).
Normally this option is not specified, in which case the structure is
represented in an implementation-dependent manner.
vector
This produces the same result as specifying (vector t). The
structure is represented as a general vector, storing
components as vector elements. The first component is
vector element 1 if the structure is :named, and element 0
otherwise.
(vector element-type)
The structure is represented as a (possibly specialized)
vector, storing components as vector elements. Every
component must be of a type that can be stored in a vector
of the type specified. The first component is vector
element 1 if the structure is :named, and element 0
otherwise. The structure may be :named only if the type
symbol is a subtype of the specified element-type.
list
The structure is represented as a list. The first component
is the cadr if the structure is :named, and the car if it
is :unnamed.
:named
The :named option specifies that the structure is ``named''; this option
takes no argument. If no :type option is specified, then the structure is
always named; so this option is useful only in conjunction with the :type
option. See section 19.7 for a further description of this option.
:initial-offset
This allows you to tell defstruct to skip over a certain number of slots
before it starts allocating the slots described in the body. This option
requires an argument, a non-negative integer, which is the number of slots
you want defstruct to skip. The :initial-offset option may be used only if
the :type option is also specified. See section 19.7.3 for a further
description of this option.
-------------------------------------------------------------------------------
19.6. By-Position Constructor Functions
If the :constructor option is given as (:constructor name arglist), then
instead of making a keyword-driven constructor function, defstruct defines a
``positional'' constructor function, taking arguments whose meaning is
determined by the argument's position rather than by a keyword. The arglist is
used to describe what the arguments to the constructor will be. In the simplest
case something like (:constructor make-foo (a b c)) defines make-foo to be a
three-argument constructor function whose arguments are used to initialize the
slots named a, b, and c.
In addition, the keywords &optional, &rest, and &aux are recognized in the
argument list. They work in the way you might expect, but there are a few fine
points worthy of explanation. Consider this example:
(:constructor create-foo
(a &optional b (c 'sea) &rest d &aux e (f 'eff)))
This defines create-foo to be a constructor of one or more arguments. The first
argument is used to initialize the a slot. The second argument is used to
initialize the b slot. If there isn't any second argument, then the default
value given in the body of the defstruct (if given) is used instead. The third
argument is used to initialize the c slot. If there isn't any third argument,
then the symbol sea is used instead. Any arguments following the third argument
are collected into a list and used to initialize the d slot. If there are three
or fewer arguments, then nil is placed in the d slot. The e slot is not
initialized; its initial value is undefined. Finally, the f slot is initialized
to contain the symbol eff.
The actions taken in the b and e cases were carefully chosen to allow the user
to specify all possible behaviors. Note that the &aux ``variables'' can be used
to completely override the default initializations given in the body.
With this definition, one can write
(create-foo 1 2)
instead of
(make-foo :a 1 :b 2)
and of course create-foo provides defaulting different from that of make-foo.
It is permissible to use the :constructor option more than once, so that you
can define several different constructor functions, each taking different
parameters.
Because a constructor of this type operates By Order of Arguments, it is
sometimes known as a BOA constructor.
[change_begin]
X3J13 voted in January 1989 (DEFSTRUCT-CONSTRUCTOR-KEY-MIXTURE) to allow &key
and &allow-other-keys in the parameter list of a ``positional'' constructor.
The initialization of slots corresponding to keyword parameters is performed in
the same manner as for &optional parameters. A variant of the example shown
above illustrates this:
(:constructor create-foo
(a &optional b (c 'sea)
&key p (q 'cue) ((:why y)) ((:you u) 'ewe)
&aux e (f 'eff)))
The treatment of slots a, b, c, e, and f is the same as in the original
example. In addition, if there is a :p keyword argument, it is used to
initialize the p slot; if there isn't any :p keyword argument, then the default
value given in the body of the defstruct (if given) is used instead. Similarly,
if there is a :q keyword argument, it is used to initialize the q slot; if
there isn't any :q keyword argument, then the symbol cue is used instead.
In order thoroughly to flog this presumably already dead horse, we further
observe that if there is a :why keyword argument, it is used to initialize the
y slot; otherwise the default value for slot y is used instead. Similarly, if
there is a :you keyword argument, it is used to initialize the u slot;
otherwise the symbol ewe is used instead.
If memory serves me correctly, defstruct was included in the original design
for Common Lisp some time before keyword arguments were approved. The failure
of positional constructors to accept keyword arguments may well have been an
oversight on my part; there is no logical reason to exclude them. I am grateful
to X3J13 for rectifying this.
A remaining difficulty is that the possibility of keyword arguments renders the
term ``positional constructor'' a misnomer. Worse yet, it ruins the term ``BOA
constructor.'' I suggest that they continue to be called BOA constructors, as I
refuse to abandon a good pun. (I regret appearing to have more compassion for
puns than for horses.)
As part of the same vote X3J13 also changed defstruct to allow BOA constructors
to have parameters (including supplied-p parameters) that do not correspond to
any slot. Such parameters may be used in subsequent initialization forms in the
parameter list. Consider this example:
(defstruct (ice-cream-factory
(:constructor fabricate-factory
(&key (capacity 5)
location
(local-flavors
(case location
((hawaii) '(pineapple macadamia guava))
((massachusetts) '(lobster baked-bean))
((california) '(ginger lotus avocado
bean-sprout garlic))
((texas) '(jalapeno barbecue))))
(flavors (subseq (append local-flavors
'(vanilla
chocolate
strawberry
pistachio
maple-walnut
peppermint))
0 capacity)))))
(capacity 3)
(flavors '(vanilla chocolate strawberry mango)))
The structure type ice-cream-factory has two constructors. The standard
constructor, make-ice-cream-factory, takes two keyword arguments named
:capacity and :flavors. For this constructor, the default for the capacity slot
is 3 and the default list of flavors is America's favorite threesome and a dark
horse (not a dead one). The BOA constructor fabricate-factory accepts four
different keyword arguments. The :capacity argument defaults to 5, and the
:flavors argument defaults in a complicated manner based on the other three.
The :local-flavors argument may be specified directly, or may be allowed to
default based on the :location of the factory. Here are examples of various
factories:
(setq houston (fabricate-factory :capacity 4 :location 'texas))
(setq cambridge (fabricate-factory :location 'massachusetts))
(setq seattle (fabricate-factory :local-flavors '(salmon)))
(setq wheaton (fabricate-factory :capacity 4 :location 'illinois))
(setq pittsburgh (fabricate-factory :capacity 4))
(setq cleveland (make-factory :capacity 4))
(ice-cream-factory-flavors houston)
=> (jalapeno barbecue vanilla chocolate)
(ice-cream-factory-flavors cambridge)
=> (lobster baked-bean vanilla chocolate strawberry)
(ice-cream-factory-flavors seattle)
=> (salmon vanilla chocolate strawberry pistachio)
(ice-cream-factory-flavors wheaton)
=> (vanilla chocolate strawberry pistachio)
(ice-cream-factory-flavors pittsburgh)
=> (vanilla chocolate strawberry pistachio)
(ice-cream-factory-flavors cleveland)
=> (vanilla chocolate strawberry mango)
[change_end]
-------------------------------------------------------------------------------
19.7. Structures of Explicitly Specified Representational Type
Sometimes it is important to have explicit control over the representation of a
structure. The :type option allows one to specify that a structure must be
implemented in a particular way, using a list or a specific kind of vector, and
to specify the exact allocation of structure slots to components of the
representation. A structure may also be ``unnamed'' or ``named,'' according to
whether the structure name is stored in (and thus recoverable from) the
structure.
-------------------------------------------------------------------------------
* Unnamed Structures
* Named Structures
* Other Aspects of Explicitly Specified Structures
-------------------------------------------------------------------------------
19.7.1. Unnamed Structures
Sometimes a particular data representation is imposed by external requirements,
and yet it is desirable to document the data format as a defstruct-style
structure. For example, consider expressions built up from numbers, symbols,
and binary operations such as + and *. An operation might be represented as it
is in Lisp, as a list of the operator and the two operands. This fact can be
expressed succinctly with defstruct in this manner:
(defstruct (binop (:type list))
(operator '? :type symbol)
operand-1
operand-2)
This will define a constructor function make-binop and three selector
functions, namely binop-operator, binop-operand-1, and binop-operand-2. (It
will not, however, define a predicate binop-p, for reasons explained below.)
The effect of make-binop is simply to construct a list of length 3:
(make-binop :operator '+ :operand-1 'x :operand-2 5)
=> (+ x 5)
(make-binop :operand-2 4 :operator '*)
=> (* nil 4)
It is just like the function list except that it takes keyword arguments and
performs slot defaulting appropriate to the binop conceptual data type.
Similarly, the selector functions binop-operator, binop-operand-1, and
binop-operand-2 are essentially equivalent to car, cadr, and caddr,
respectively. (They might not be completely equivalent because, for example, an
implementation would be justified in adding error-checking code to ensure that
the argument to each selector function is a length-3 list.)
We speak of binop as being a ``conceptual'' data type because binop is not made
a part of the Common Lisp type system. The predicate typep will not recognize
binop as a type specifier, and type-of will return list when given a binop
structure. Indeed, there is no way to distinguish a data structure constructed
by make-binop from any other list that happens to have the correct structure.
There is not even any way to recover the structure name binop from a structure
created by make-binop. This can be done, however, if the structure is
``named.''
-------------------------------------------------------------------------------
19.7.2. Named Structures
A ``named'' structure has the property that, given an instance of the
structure, the structure name (that names the type) can be reliably recovered.
For structures defined with no :type option, the structure name actually
becomes part of the Common Lisp data-type system. The function type-of, when
applied to such a structure, will return the structure name as the type of the
object; the predicate typep will recognize the structure name as a valid type
specifier.
For structures defined with a :type option, type-of will return a type
specifier such as list or (vector t), depending on the type specified to the
:type option. The structure name does not become a valid type specifier.
However, if the :named option is also specified, then the first component of
the structure (as created by a defstruct constructor function) will always
contain the structure name. This allows the structure name to be recovered from
an instance of the structure and allows a reasonable predicate for the
conceptual type to be defined: the automatically defined name-p predicate for
the structure operates by first checking that its argument is of the proper
type (list, (vector t), or whatever) and then checking whether the first
component contains the appropriate type name.
Consider the binop example shown above, modified only to include the :named
option:
(defstruct (binop (:type list) :named)
(operator '? :type symbol)
operand-1
operand-2)
As before, this will define a constructor function make-binop and three
selector functions binop-operator, binop-operand-1, and binop-operand-2. It
will also define a predicate binop-p.
The effect of make-binop is now to construct a list of length 4:
(make-binop :operator '+ :operand-1 'x :operand-2 5)
=> (binop + x 5)
(make-binop :operand-2 4 :operator '*)
=> (binop * nil 4)
The structure has the same layout as before except that the structure name
binop is included as the first list element. The selector functions
binop-operator, binop-operand-1, and binop-operand-2 are essentially equivalent
to cadr, caddr, and cadddr, respectively. The predicate binop-p is more or less
equivalent to the following definition.
(defun binop-p (x)
(and (consp x) (eq (car x) 'binop)))
The name binop is still not a valid type specifier recognizable to typep, but
at least there is a way of distinguishing binop structures from other similarly
defined structures.
-------------------------------------------------------------------------------
19.7.3. Other Aspects of Explicitly Specified Structures
The :initial-offset option allows one to specify that slots be allocated
beginning at a representational element other than the first. For example, the
form
(defstruct (binop (:type list) (:initial-offset 2))
(operator '? :type symbol)
operand-1
operand-2)
would result in the following behavior for make-binop:
(make-binop :operator '+ :operand-1 'x :operand-2 5)
=> (nil nil + x 5)
(make-binop :operand-2 4 :operator '*)
=> (nil nil * nil 4)
The selectors binop-operator, binop-operand-1, and binop-operand-2 would be
essentially equivalent to caddr, cadddr, and car of cddddr, respectively.
Similarly, the form
(defstruct (binop (:type list) :named (:initial-offset 2))
(operator '? :type symbol)
operand-1
operand-2)
would result in the following behavior for make-binop:
(make-binop :operator '+ :operand-1 'x :operand-2 5)
=> (nil nil binop + x 5)
(make-binop :operand-2 4 :operator '*)
=> (nil nil binop * nil 4)
If the :include is used with the :type option, then the effect is first to skip
over as many representation elements as needed to represent the included
structure, then to skip over any additional elements specified by the
:initial-offset option, and then to begin allocation of elements from that
point. For example:
(defstruct (binop (:type list) :named (:initial-offset 2))
(operator '? :type symbol)
operand-1
operand-2)
(defstruct (annotated-binop (:type list)
(:initial-offset 3)
(:include binop))
commutative associative identity)
(make-annotated-binop :operator '*
:operand-1 'x
:operand-2 5
:commutative t
:associative t
:identity 1)
=> (nil nil binop * x 5 nil nil nil t t 1)
The first two nil elements stem from the :initial-offset of 2 in the definition
of binop. The next four elements contain the structure name and three slots for
binop. The next three nil elements stem from the :initial-offset of 3 in the
definition of annotated-binop. The last three list elements contain the
additional slots for an annotated-binop.
-------------------------------------------------------------------------------